/**
 * @file main.cpp
 * @author SDK_TEAM
 * @brief
 * @version 0.1
 * @date 2022-10-26
 *
 * Copyright:
 * © 2018 北京灵汐科技有限公司 版权所有。
 * 注意：以下内容均为北京灵汐科技有限公司原创，未经本公司允许，不得转载，否则将视为侵权；对于不遵守此声明或者其他违法使用以下内容者，本公司依法保留追究权。
 * © 2018 Lynxi Technologies Co., Ltd. All rights reserved.
 * NOTICE: All information contained here is, and remains the property of Lynxi.
 * This file can not be copied or distributed without the permission of Lynxi
 * Technologies Co., Ltd.
 *
 */

#include <lyn_api.h>
#include <lyn_blas.h>
#include <nlohmann/json.hpp>
#include <string>
#include "imageDecoder.h"
#include "imageEncoder.h"
#include "ipeParamFaceRecognition.h"
#include "opencv2/opencv.hpp"
#include "osd_plugin.h"
#include "face_post_process.h"
#include "typeConv.hpp"
#include "util.hpp"
#include "CLI/CLI11.hpp" // 引入 CLI11 头文件

using namespace std;

namespace FaceAttributes
{
  struct FaceRect
  {
  private:
    size_t EnsureEven(size_t n)
    {
      if (n % 2 != 0)
      {
        return n + 1;
      }
      return n;
    }

  public:
    size_t x, y, w, h;

    void MakeEven()
    {
      x = EnsureEven(x);
      y = EnsureEven(y);
      w = EnsureEven(w);
      h = EnsureEven(h);
    }
  };

  class FeatureData
  {
  public:
    FeatureData() : numFeature(0), features(nullptr) {}

    FeatureData(int num)
        : features(std::shared_ptr<float>(new float[num],
                                          [](float *p)
                                          { delete[] p; }))
    {
      numFeature = num;
    }

    ~FeatureData() {}

    int numFeature;
    std::shared_ptr<float> features;

    static void getAffineTransform(const float *points_from,
                                   const float *points_to, int num_point,
                                   float *tm, float (*tmatrix)[3])
    {
      float ma[4][4] = {{0.f}};
      float mb[4] = {0.f};
      float mm[4];

      for (int i = 0; i < num_point; i++)
      {
        ma[0][0] +=
            points_from[0] * points_from[0] + points_from[1] * points_from[1];
        ma[0][2] += points_from[0];
        ma[0][3] += points_from[1];

        mb[0] += points_from[0] * points_to[0] + points_from[1] * points_to[1];
        mb[1] += points_from[0] * points_to[1] - points_from[1] * points_to[0];
        mb[2] += points_to[0];
        mb[3] += points_to[1];

        points_from += 2;
        points_to += 2;
      }

      ma[1][1] = ma[0][0];
      ma[2][1] = ma[1][2] = -ma[0][3];
      ma[3][1] = ma[1][3] = ma[2][0] = ma[0][2];
      ma[2][2] = ma[3][3] = (float)num_point;
      ma[3][0] = ma[0][3];

      // MM = inv(A) * B
      // matrix 4x4 invert by https://github.com/willnode/N-Matrix-Programmer
      // suppose the user provide valid points combination
      // I have not taken det == zero into account here   :>  --- nihui
      float mai[4][4];
      float det;
      // clang-format off
    // *INDENT-OFF*
    {
        float A2323 = ma[2][2] * ma[3][3] - ma[2][3] * ma[3][2];
        float A1323 = ma[2][1] * ma[3][3] - ma[2][3] * ma[3][1];
        float A1223 = ma[2][1] * ma[3][2] - ma[2][2] * ma[3][1];
        float A0323 = ma[2][0] * ma[3][3] - ma[2][3] * ma[3][0];
        float A0223 = ma[2][0] * ma[3][2] - ma[2][2] * ma[3][0];
        float A0123 = ma[2][0] * ma[3][1] - ma[2][1] * ma[3][0];
        float A2313 = ma[1][2] * ma[3][3] - ma[1][3] * ma[3][2];
        float A1313 = ma[1][1] * ma[3][3] - ma[1][3] * ma[3][1];
        float A1213 = ma[1][1] * ma[3][2] - ma[1][2] * ma[3][1];
        float A2312 = ma[1][2] * ma[2][3] - ma[1][3] * ma[2][2];
        float A1312 = ma[1][1] * ma[2][3] - ma[1][3] * ma[2][1];
        float A1212 = ma[1][1] * ma[2][2] - ma[1][2] * ma[2][1];
        float A0313 = ma[1][0] * ma[3][3] - ma[1][3] * ma[3][0];
        float A0213 = ma[1][0] * ma[3][2] - ma[1][2] * ma[3][0];
        float A0312 = ma[1][0] * ma[2][3] - ma[1][3] * ma[2][0];
        float A0212 = ma[1][0] * ma[2][2] - ma[1][2] * ma[2][0];
        float A0113 = ma[1][0] * ma[3][1] - ma[1][1] * ma[3][0];
        float A0112 = ma[1][0] * ma[2][1] - ma[1][1] * ma[2][0];

        det = ma[0][0] * (ma[1][1] * A2323 - ma[1][2] * A1323 + ma[1][3] * A1223)
            - ma[0][1] * (ma[1][0] * A2323 - ma[1][2] * A0323 + ma[1][3] * A0223)
            + ma[0][2] * (ma[1][0] * A1323 - ma[1][1] * A0323 + ma[1][3] * A0123)
            - ma[0][3] * (ma[1][0] * A1223 - ma[1][1] * A0223 + ma[1][2] * A0123);

        det = 1.f / det;

        mai[0][0] =   (ma[1][1] * A2323 - ma[1][2] * A1323 + ma[1][3] * A1223);
        mai[0][1] = - (ma[0][1] * A2323 - ma[0][2] * A1323 + ma[0][3] * A1223);
        mai[0][2] =   (ma[0][1] * A2313 - ma[0][2] * A1313 + ma[0][3] * A1213);
        mai[0][3] = - (ma[0][1] * A2312 - ma[0][2] * A1312 + ma[0][3] * A1212);
        mai[1][0] = - (ma[1][0] * A2323 - ma[1][2] * A0323 + ma[1][3] * A0223);
        mai[1][1] =   (ma[0][0] * A2323 - ma[0][2] * A0323 + ma[0][3] * A0223);
        mai[1][2] = - (ma[0][0] * A2313 - ma[0][2] * A0313 + ma[0][3] * A0213);
        mai[1][3] =   (ma[0][0] * A2312 - ma[0][2] * A0312 + ma[0][3] * A0212);
        mai[2][0] =   (ma[1][0] * A1323 - ma[1][1] * A0323 + ma[1][3] * A0123);
        mai[2][1] = - (ma[0][0] * A1323 - ma[0][1] * A0323 + ma[0][3] * A0123);
        mai[2][2] =   (ma[0][0] * A1313 - ma[0][1] * A0313 + ma[0][3] * A0113);
        mai[2][3] = - (ma[0][0] * A1312 - ma[0][1] * A0312 + ma[0][3] * A0112);
        mai[3][0] = - (ma[1][0] * A1223 - ma[1][1] * A0223 + ma[1][2] * A0123);
        mai[3][1] =   (ma[0][0] * A1223 - ma[0][1] * A0223 + ma[0][2] * A0123);
        mai[3][2] = - (ma[0][0] * A1213 - ma[0][1] * A0213 + ma[0][2] * A0113);
        mai[3][3] =   (ma[0][0] * A1212 - ma[0][1] * A0212 + ma[0][2] * A0112);
    }
    // *INDENT-ON*
      // clang-format on

      mm[0] = det * (mai[0][0] * mb[0] + mai[0][1] * mb[1] + mai[0][2] * mb[2] +
                     mai[0][3] * mb[3]);
      mm[1] = det * (mai[1][0] * mb[0] + mai[1][1] * mb[1] + mai[1][2] * mb[2] +
                     mai[1][3] * mb[3]);
      mm[2] = det * (mai[2][0] * mb[0] + mai[2][1] * mb[1] + mai[2][2] * mb[2] +
                     mai[2][3] * mb[3]);
      mm[3] = det * (mai[3][0] * mb[0] + mai[3][1] * mb[1] + mai[3][2] * mb[2] +
                     mai[3][3] * mb[3]);

      tm[0] = tm[4] = mm[0];
      tm[1] = -mm[1];
      tm[3] = mm[1];
      tm[2] = mm[2];
      tm[5] = mm[3];

      tmatrix[0][0] = tmatrix[1][1] = mm[0];
      tmatrix[0][1] = -mm[1];
      tmatrix[1][0] = mm[1];
      tmatrix[0][2] = mm[2];
      tmatrix[1][2] = mm[3];
    }

    static void getTransformMat(const float (&landmark)[10], const FaceRect &rect,
                                float (&matrix)[2][3])
    {
      const float point_landmark[10] = {
          // +8 是因为我们处理112*112的图
          30.2946f + 8.0f,
          51.6963f,
          65.5318f + 8.0f,
          51.5014f,
          48.0252f + 8.0f,
          71.7366f,
          33.5493f + 8.0f,
          92.3655f,
          62.729904f + 8.0f,
          92.2041f,
      };
      const size_t NUM_LANDMARKS = 5;

      auto tmp_landmarks = make_shared<FeatureData>(NUM_LANDMARKS * 2);
      float *landmarksPtr = tmp_landmarks->features.get();
      for (size_t ll = 0; ll < NUM_LANDMARKS * 2; ll++)
      {
        landmarksPtr[ll] = landmark[ll];
      }

      std::vector<float> landmarks(NUM_LANDMARKS * 2);
      for (size_t i = 0; i < NUM_LANDMARKS; i++)
      {
        landmarks[2 * i] = landmarksPtr[2 * i] - rect.x;
        landmarks[2 * i + 1] = landmarksPtr[2 * i + 1] - rect.y;

        landmarks[2 * i] = landmarks[2 * i] < 0 ? 0 : landmarks[2 * i];
        landmarks[2 * i + 1] = landmarks[2 * i + 1] < 0 ? 0 : landmarks[2 * i + 1];
      }
      float tm[6]; // 用来对齐数据
      getAffineTransform(landmarks.data(), point_landmark, NUM_LANDMARKS, tm,
                         matrix);
    }
  };
} // namespace FaceAttributes

static FaceAttributes::FaceRect ExtendFaceBox(const FaceDetectBox &faceBox,
                                              size_t imagePixelW,
                                              size_t imagePixelH)
{
  float w = faceBox.xmax - faceBox.xmin;
  float h = faceBox.ymax - faceBox.ymin;

  size_t xmin = max(int(faceBox.xmin), (int)0);
  size_t ymin = max(int(faceBox.ymin), (int)0);
  size_t xmax = min(int(faceBox.xmax), int(imagePixelW - 1));
  size_t ymax = min(int(faceBox.ymax), int(imagePixelH - 1));

  auto rect = FaceAttributes::FaceRect{
      xmin,
      ymin,
      xmax - xmin,
      ymax - ymin,
  };
  rect.MakeEven();
  return rect;
}

inline bool CheckFaceBox(const FaceDetectBox &faceBox)
{
  float w = faceBox.xmax - faceBox.xmin;
  float h = faceBox.ymax - faceBox.ymin;

  if (w <= 20 || h <= 20)
  {
    return false;
  }

  return true;
}

int main(int argc, char *argv[])
{
  string inputFile;
  string outputFile;
  int deviceId;
  string faceLibConfig;
  string faceDetectModel;
  string faceDetectPostPluginPath;
  string faceFeatureModel;
  string argOsdPluginPath;

  CLI::App app;
  // 添加选项
  app.add_option("-i", inputFile, "input jpeg file path")->default_val("../data/face_recognition_with_image_sample/test.jpeg");
  app.add_option("-o", outputFile, "output jpeg file path")->default_val("../output.jpeg");
  app.add_option("-d", deviceId, "lynxi device id")->default_val(0);
  app.add_option("--face_lib_config", faceLibConfig, "input face lib config file")->default_val("../data/face_recognition_with_image_sample/face_config.json");
  app.add_option("--m1,--face_detect_model", faceDetectModel, "face detect model path")->default_val("../model/scrfd_500m_b1_h640_w640_c3_iuint8_ofloat16_0000/Net_0");
  app.add_option("-p,--face_detect_post_plugin", faceDetectPostPluginPath, "face detect post process plugin path")->default_val("../common/plugin/postprocess/cross_compiled_lib/libScrfdPostPlugin.so");
  app.add_option("--m2,--face_feature_model", faceFeatureModel, "face detect model path")->default_val("../model/w600k_mbf_b1_h112_w112_c3_iuint8_ofloat16_0000/Net_0");
  app.add_option("--osd_plugin", argOsdPluginPath, "osd plugin path")->default_val("../common/plugin/osd/lib/libOsdPlugin.so");

  // 解析命令行参数
  CLI11_PARSE(app, argc, argv);

  if (!IsFileExist(inputFile))
  {
    cout << "error : input jpeg file " << inputFile << " is not exsit." << endl;
    return 0;
  }

  if (!IsValidDevice(deviceId))
  {
    cout << "error : device id is invalid." << endl;
    return 0;
  }

  if (!IsFileExist(faceLibConfig))
  {
    cout << "error : face lib config " << faceLibConfig << " is not exsit." << endl;
    return 0;
  }

  if (!IsFileExist(faceDetectModel))
  {
    cout << "error : face detect model is not exsit." << endl;
    return 0;
  }

  if (!IsFileExist(faceDetectPostPluginPath))
  {
    cout << "error : face detect post process plugin is not exsit." << endl;
    return 0;
  }

  if (!IsFileExist(faceFeatureModel))
  {
    cout << "error : face feature model is not exsit." << endl;
    return 0;
  }

  if (!IsFileExist(argOsdPluginPath))
  {
    cout << "error : osd plugin is not exsit." << endl;
    return 0;
  }

  ifstream faces_config_file(faceLibConfig);
  if (!faces_config_file.is_open())
  {
    const string err = "cannot open facelib config file: " + faceLibConfig;
    throw invalid_argument(err);
  }
  auto faces_config = nlohmann::json::parse(faces_config_file);
  vector<string> face_lib_labels = faces_config["names"].get<vector<string>>();
  const string features_path = faces_config["features"].get<string>();
  ifstream features_bin(features_path);
  if (!features_bin.is_open())
  {
    const string err = "cannot open facelib bin file: " + features_path;
    throw invalid_argument(err);
  }

  // 2. 创建 context 与 stream
  lynContext_t ctx = nullptr;
  CHECK_ERR(lynCreateContext(&ctx, deviceId));
  CHECK_ERR(lynRegisterErrorHandler(StreamErrorHandler, nullptr));
  CHECK_ERR(lynCosineInit());
  lynStream_t stream = nullptr;
  CHECK_ERR(lynCreateStream(&stream));

  // 3. 初始化解码图片类
  cout << "image decode" << endl;
  ImageDecoder imageDec;
  imageDec.Init(inputFile, false);
  lynImageInfo_t imgInfo;
  imageDec.GetImageDecInfo(&imgInfo);

  // 4. 同步解码图片到 Device 侧
  lynCodecBuf_t decodeImg;
  decodeImg.size = imgInfo.output.predictBufSize;
  CHECK_ERR(lynMalloc((void **)&decodeImg.data, decodeImg.size));
  imageDec.DecodeImageToDevice(stream, decodeImg.data);

  // 5. 加载模型
  ModelInfo faceDetectModelInfo;
  faceDetectModelInfo.LoadModelByPath(faceDetectModel.c_str());
  ModelInfo faceFeatureModelInfo;
  faceFeatureModelInfo.LoadModelByPath(faceFeatureModel.c_str());

  // 加载lynxi plugin后处理和osd
  lynPlugin_t faceDetectPlugin;
  CHECK_ERR(
      lynPluginRegister(&faceDetectPlugin, faceDetectPostPluginPath.c_str()));
  lynPlugin_t osdPlugin;
  CHECK_ERR(lynPluginRegister(&osdPlugin, argOsdPluginPath.c_str()));

  // 读取人脸库信息
  uint8_t *face_lib_features_host =
      new uint8_t[faceFeatureModelInfo.outputSize * face_lib_labels.size()];
  void *face_lib_features = nullptr;
  CHECK_ERR(
      lynMalloc((void **)&face_lib_features,
                faceFeatureModelInfo.outputSize * face_lib_labels.size()));
  features_bin.read((char *)face_lib_features_host,
                    faceFeatureModelInfo.outputSize * face_lib_labels.size());
  CHECK_ERR(lynMemcpy(face_lib_features, face_lib_features_host,
                      faceFeatureModelInfo.outputSize * face_lib_labels.size(),
                      ClientToServer));
  delete[] face_lib_features_host;

  // 6. 人脸检测IPE处理
  cout << "detect faces" << endl;
  uint8_t *pDetectIpeBuf = nullptr;
  CHECK_ERR(lynMalloc((void **)&pDetectIpeBuf, faceDetectModelInfo.inputSize));
  IpeParamFace ipeFace(faceDetectModelInfo.width, faceDetectModelInfo.height);
  ipeFace.SetImgInfo(imgInfo.output.width, imgInfo.output.height,
                     LYN_PIX_FMT_NV12);
  ipeFace.CalcParam(stream, decodeImg.data, pDetectIpeBuf);

  // 7. 调用 APU 推理接口
  uint8_t *pDevDetectApuBuf = nullptr;
  CHECK_ERR(
      lynMalloc((void **)&pDevDetectApuBuf, faceDetectModelInfo.outputSize));
  CHECK_ERR(lynExecuteModelAsync(stream, faceDetectModelInfo.model,
                                 pDetectIpeBuf, pDevDetectApuBuf,
                                 faceDetectModelInfo.batchSize));

  // 8. 加载人脸检测post Plugin, 进行模型后处理
  FacePostProcessInfo_t post_info;
  memset(&post_info, 0, sizeof(FacePostProcessInfo_t));

  post_info.score_threshold = 0.25;
  post_info.nms_threshold = 0.45;
  post_info.width = faceDetectModelInfo.width;
  post_info.height = faceDetectModelInfo.height;
  post_info.ori_width = imgInfo.output.width;
  post_info.ori_height = imgInfo.output.height;
  FaceDetectInfo *pDevDetectInfo;
  CHECK_ERR(lynMalloc((void **)&pDevDetectInfo, sizeof(FaceDetectInfo)));
  post_info.output_tensor = pDevDetectApuBuf;
  post_info.detectInfo = pDevDetectInfo;

  CHECK_ERR(lynPluginRunAsync(stream, faceDetectPlugin, "lynFacePostProcess",
                              &post_info, sizeof(post_info)));

  FaceDetectInfo *pHostDetectInfo = new FaceDetectInfo;
  CHECK_ERR(lynMemcpyAsync(stream, pHostDetectInfo, pDevDetectInfo,
                           sizeof(FaceDetectInfo), ServerToClient));
  lynSynchronizeStream(stream);

  // 分析人脸特征
  void *pDevFeatureApuBuf = nullptr;
  lynBoxesInfo *pHostBoxesInfo = nullptr;
  vector<int> faceIndexVec;
  if (pHostDetectInfo->boxesNum)
  {
    cout << "get face features" << endl;
    CHECK_ERR(
        lynMalloc((void **)&pDevFeatureApuBuf,
                  faceFeatureModelInfo.outputSize * pHostDetectInfo->boxesNum));
    pHostBoxesInfo = new lynBoxesInfo;
    memset(pHostBoxesInfo, 0, sizeof(lynBoxesInfo));
    pHostBoxesInfo->boxesNum = pHostDetectInfo->boxesNum;
  }
  else
  {
    cout << "no face detected" << endl;
  }

  for (int faceIndex = 0; faceIndex < pHostDetectInfo->boxesNum; faceIndex++)
  {
    const auto box = pHostDetectInfo->boxes[faceIndex];
    auto faceRect =
        ExtendFaceBox(box, imgInfo.output.width, imgInfo.output.height);
    if (CheckFaceBox(box))
    {
      // 人脸裁剪
      IpeParamCrop *ipeCrop =
          new IpeParamCrop(faceRect.x, faceRect.y, faceRect.w, faceRect.h);
      uint8_t *pFaceCropIpeBufOut = nullptr;
      CHECK_ERR(
          lynMalloc((void **)&pFaceCropIpeBufOut, faceRect.w * faceRect.h * 3));
      ipeCrop->SetImgInfo(imgInfo.output.width, imgInfo.output.height,
                          LYN_PIX_FMT_NV12);
      ipeCrop->CalcParam(stream, decodeImg.data, pFaceCropIpeBufOut);

      // 特征模型预处理
      float transformMat[2][3];
      FaceAttributes::FeatureData::getTransformMat(box.landmark, faceRect,
                                                   transformMat);
      float (*pMat)[3];
      pMat = new float[2][3];
      memcpy(pMat, transformMat, sizeof(transformMat));
      IpeParamAffine *ipeAffine = new IpeParamAffine(
          faceFeatureModelInfo.width, faceFeatureModelInfo.height, pMat,
          0, 0, 0);
      uint8_t *pFeatureIpeBuf = nullptr;
      CHECK_ERR(
          lynMalloc((void **)&pFeatureIpeBuf, faceFeatureModelInfo.inputSize));
      ipeAffine->SetImgInfo(faceRect.w, faceRect.h, LYN_PIX_FMT_RGB24);
      ipeAffine->CalcParam(stream, pFaceCropIpeBufOut, pFeatureIpeBuf);

      CHECK_ERR(lynExecuteModelAsync(
          stream, faceFeatureModelInfo.model, pFeatureIpeBuf,
          (uint8_t *)pDevFeatureApuBuf +
              faceFeatureModelInfo.outputSize * faceIndexVec.size(),
          faceFeatureModelInfo.batchSize));
      faceIndexVec.push_back(faceIndex);
      SetCallback(stream, [ipeCrop, pFaceCropIpeBufOut, ipeAffine,
                           pFeatureIpeBuf, pMat]
                  {
        CHECK_ERR(lynFree(pFaceCropIpeBufOut));
        CHECK_ERR(lynFree(pFeatureIpeBuf));
        delete ipeCrop;
        delete ipeAffine;
        delete[] pMat; });
    }
    pHostBoxesInfo->boxes[faceIndex].xmin = faceRect.x;
    pHostBoxesInfo->boxes[faceIndex].ymin = faceRect.y;
    pHostBoxesInfo->boxes[faceIndex].xmax = faceRect.x + faceRect.w;
    pHostBoxesInfo->boxes[faceIndex].ymax = faceRect.y + faceRect.h;
  }

  // 人脸比对
  if (faceIndexVec.size() > 0)
  {
    cout << "match faces" << endl;
    void *pDevCosineBuf;
    CHECK_ERR(
        lynMalloc((void **)&pDevCosineBuf,
                  faceIndexVec.size() * (sizeof(int16_t) + sizeof(float))));
    CHECK_ERR(lynExecuteCosineAsync(
        stream, LYN_TRANS, faceIndexVec.size(), face_lib_labels.size(), 1,
        pDevFeatureApuBuf, DT_FLOAT16, face_lib_features, DT_FLOAT16,
        pDevCosineBuf));
    uint8_t *pHostCosineBuf =
        new uint8_t[faceIndexVec.size() * (sizeof(int16_t) + sizeof(float))];
    CHECK_ERR(
        lynMemcpyAsync(stream, pHostCosineBuf, pDevCosineBuf,
                       faceIndexVec.size() * (sizeof(int16_t) + sizeof(float)),
                       ServerToClient));
    SetCallback(stream, [pDevCosineBuf, pHostCosineBuf, faceIndexVec,
                         pHostDetectInfo, pHostBoxesInfo, face_lib_labels]
                {
      int faceCount = faceIndexVec.size();
      for (int i = 0; i < faceCount; i++) {
        auto face_index = faceIndexVec[i];
        auto idx = *(float *)(pHostCosineBuf + faceCount * sizeof(int16_t) +
                              i * sizeof(float));
        auto score = COMMON::half2float(
            *(int16_t *)(pHostCosineBuf + i * sizeof(int16_t)));
        if (idx >= 0 && idx < face_lib_labels.size()) {
          string name = "unknown";
          if (score > 5) {
            name = face_lib_labels[idx];
            cout << name << " is matched" << endl;
          }

          strcpy(pHostBoxesInfo->boxes[face_index].label, name.c_str());
        } else {
          cout << "invalid consine execute result idx: " << idx << endl;
        }
      }
      CHECK_ERR(lynFree(pDevCosineBuf));
      delete[] pHostCosineBuf;
      delete pHostDetectInfo; });
    lynSynchronizeStream(stream);
  }

  // 9. 加载osd Plugin, 将结果叠加到图像上
  if (pHostBoxesInfo)
  {
    lynBoxesInfo *pDevBoxesInfo;
    CHECK_ERR(lynMalloc((void **)&pDevBoxesInfo, sizeof(lynBoxesInfo)));
    CHECK_ERR(lynMemcpyAsync(stream, pDevBoxesInfo, pHostBoxesInfo,
                             sizeof(lynBoxesInfo), ClientToServer));

    lynDrawBoxAndTextPara para;
    para.imgData = decodeImg.data;
    para.imgFmt = LYN_PIX_FMT_NV12;
    para.imgW = imgInfo.output.width;
    para.imgH = imgInfo.output.height;
    para.boxesInfo = pDevBoxesInfo;
    para.boxColor = DRAW_COLOR_BLUE;
    para.boxThick = DRAW_THICK_2;
    para.fontSize = FONT_SIZE_24;
    para.fontColor = DRAW_COLOR_BLUE;
    CHECK_ERR(lynPluginRunAsync(stream, osdPlugin, "lynDrawBoxAndText", &para,
                                sizeof(para)));
    SetCallback(stream, [pDevBoxesInfo]
                { CHECK_ERR(lynFree(pDevBoxesInfo)); });
  }

  // 9. 进行图片编码
  cout << "save new image" << endl;
  ImageEncoder encoder;
  encoder.EncodeImageAndSave(stream, outputFile, decodeImg, imgInfo);

  // 10. 销毁资源
  CHECK_ERR(lynDestroyStream(stream));
  CHECK_ERR(lynPluginUnregister(faceDetectPlugin));
  CHECK_ERR(lynPluginUnregister(osdPlugin));
  CHECK_ERR(lynFree(decodeImg.data));
  CHECK_ERR(lynFree(face_lib_features));
  CHECK_ERR(lynFree(pDetectIpeBuf));
  CHECK_ERR(lynFree(pDevDetectApuBuf));
  if (pDevFeatureApuBuf)
    CHECK_ERR(lynFree(pDevFeatureApuBuf));
  if (pHostBoxesInfo)
    delete pHostBoxesInfo;
  faceDetectModelInfo.UnLoadModel();
  faceFeatureModelInfo.UnLoadModel();
  CHECK_ERR(lynCosineUninit());
  CHECK_ERR(lynDestroyContext(ctx));

  cout << "process over" << endl;

  return 0;
}
